استكشف JavaScript Module Federation لإنشاء أنظمة مكونات إضافية ديناميكية. تعلم البنية، التنفيذ، الأمان، وأفضل الممارسات للتطبيقات القابلة للتطوير والصيانة.
بنية المكونات الإضافية باستخدام JavaScript Module Federation: بناء نظام إضافات ديناميكي
في بيئة تطوير الويب المعقدة اليوم، يعد بناء تطبيقات معيارية وقابلة للتطوير والصيانة أمرًا بالغ الأهمية. إحدى التقنيات القوية لتحقيق ذلك هي من خلال بنية المكونات الإضافية (plugin architecture)، حيث يتم تقسيم الوظائف إلى وحدات مستقلة يتم تحميلها ديناميكيًا. يوفر JavaScript Module Federation، وهو ميزة في Webpack 5، آلية قوية لتنفيذ مثل هذه البنى. تتعمق هذه المقالة في تعقيدات استخدام Module Federation لبناء نظام مكونات إضافية ديناميكي.
ما هو اتحاد الوحدات النمطية (Module Federation)؟
يسمح اتحاد الوحدات النمطية لتطبيقات جافاسكريبت بمشاركة الكود ديناميكيًا في وقت التشغيل. هذا يعني أن وحدة نمطية (جزء من الكود) من تطبيق ما يمكن استخدامها مباشرة بواسطة تطبيق آخر، دون الحاجة إلى إعادة بنائها أو نشرها. يتم تحقيق ذلك عن طريق عرض واستهلاك الوحدات النمطية عبر إصدارات مختلفة وحتى عمليات نشر مختلفة.
تتطلب الطرق التقليدية لمشاركة الكود، مثل حزم npm، إعادة بناء ونشر التطبيقات المستهلكة كلما تم تحديث تبعية مشتركة. يلغي Module Federation هذه النفقات العامة، مما يجعله مثاليًا للسيناريوهات التي تتطلب تحديثات متكررة وعمليات نشر مستقلة.
لماذا نستخدم Module Federation لبنى المكونات الإضافية؟
يقدم Module Federation العديد من المزايا عند بناء بنى المكونات الإضافية:
- تحميل الوحدات الديناميكي: يمكن تحميل وتفريغ المكونات الإضافية في وقت التشغيل، مما يسمح للتطبيقات بالتكيف مع المتطلبات المتغيرة دون الحاجة إلى إعادة نشر كاملة.
- فصل المكونات: يتم تطوير ونشر المكونات الإضافية بشكل مستقل، مما يقلل من التبعيات بين الأجزاء المختلفة من التطبيق.
- قابلية التوسع: يمكن توسيع التطبيق بسهولة بإضافة مكونات إضافية جديدة دون التأثير على الوظائف الحالية.
- قابلية الصيانة: يمكن تحديث وصيانة المكونات الإضافية بشكل مستقل، مما يقلل من خطر إدخال الأخطاء في التطبيق الأساسي.
- إعادة استخدام الكود: يمكن إعادة استخدام المكونات الإضافية عبر تطبيقات متعددة، مما يعزز الاتساق ويقلل من جهد التطوير.
- إدارة الإصدارات والتراجعات: يمكنك إدارة إصدارات مختلفة من المكونات الإضافية والتراجع بسهولة إلى الإصدارات السابقة إذا لزم الأمر.
المفاهيم الأساسية: الحاويات المضيفة والبعيدة (Host and Remote Containers)
يدور Module Federation حول مفهومين رئيسيين:
- الحاوية المضيفة (Host Container): التطبيق الرئيسي الذي يستهلك الوحدات البعيدة (المكونات الإضافية).
- الحاوية البعيدة (Remote Container): التطبيق الذي يعرض الوحدات (المكونات الإضافية) ليتم استهلاكها من قبل المضيف.
تقوم الحاوية المضيفة بجلب ملف الإدخال البعيد (remote entry file) ديناميكيًا من الحاوية البعيدة، والذي يحتوي على بيان بالوحدات المعروضة. يمكن للمضيف بعد ذلك الوصول إلى هذه الوحدات واستخدامها كما لو كانت جزءًا من قاعدة الكود الخاصة به.
تنفيذ نظام مكونات إضافية ديناميكي باستخدام Module Federation: دليل خطوة بخطوة
دعنا نستعرض عملية بناء نظام مكونات إضافية بسيط باستخدام Module Federation. سنقوم بإنشاء تطبيق مضيف وتطبيق مكون إضافي بعيد.
1. إعداد التطبيق المضيف (Host Container)
أولاً، قم بإنشاء دليل مشروع جديد وتهيئته كمشروع npm جديد:
mkdir host-app
cd host-app
npm init -y
قم بتثبيت Webpack وتبعياته:
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
أنشئ ملف `webpack.config.js` في دليل `host-app` بالتكوين التالي:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
devServer: {
port: 3000,
hot: true,
static: {
directory: path.join(__dirname, 'dist'),
},
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'Host',
remotes: {
'plugin': 'Plugin@http://localhost:3001/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
الشرح:
- `name`: اسم التطبيق المضيف.
- `remotes`: يحدد الحاويات البعيدة التي سيستهلكها المضيف. في هذه الحالة، يستهلك حاوية بعيدة تسمى `plugin` من `http://localhost:3001/remoteEntry.js`. الصيغة `Plugin@` تعني أن `name` الخاص بـ ModuleFederationPlugin في التطبيق البعيد هو 'Plugin'.
- `shared`: يسرد التبعيات المشتركة بين الحاوية المضيفة والحاويات البعيدة. هذا يمنع تحميل نسخ مكررة من هذه التبعيات. استخدام `shared` أمر حاسم لتجنب الأخطاء وضمان عمل المكون الإضافي بشكل صحيح.
أنشئ دليل `src` وأضف ملف `index.js` بالمحتوى التالي:
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
const PluginComponent = React.lazy(() => import('plugin/PluginComponent'));
const App = () => {
return (
<div>
<h1>Host Application</h1>
<Suspense fallback={<div>Loading Plugin...</div>}>
<PluginComponent />
</Suspense>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
الشرح:
- نحن نستخدم `React.lazy` لاستيراد `PluginComponent` ديناميكيًا من التطبيق البعيد `plugin`. هذا أمر بالغ الأهمية للتحميل الكسول (lazy loading) للمكون الإضافي وتجنب تأخير التحميل الأولي.
- يُستخدم مكون `Suspense` للتعامل مع حالة التحميل أثناء جلب المكون الإضافي.
أنشئ دليل `public` وأضف ملف `index.html` بالمحتوى التالي:
<!DOCTYPE html>
<html>
<head>
<title>Host Application</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>
أضف ملف تكوين Babel باسم `.babelrc`:
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
حدّث ملف `package.json` الخاص بك بسكريبت بدء التشغيل:
{
"name": "host-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"babel-loader": "^9.1.3",
"html-webpack-plugin": "^5.6.0",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
2. إعداد التطبيق البعيد (Plugin Container)
أنشئ دليل مشروع جديد للمكون الإضافي:
mkdir plugin-app
cd plugin-app
npm init -y
قم بتثبيت Webpack وتبعياته:
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
أنشئ ملف `webpack.config.js` في دليل `plugin-app` بالتكوين التالي:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
devServer: {
port: 3001,
hot: true,
static: {
directory: path.join(__dirname, 'dist'),
},
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'Plugin',
filename: 'remoteEntry.js',
exposes: {
'./PluginComponent': './src/PluginComponent',
},
shared: ['react', 'react-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
الشرح:
- `name`: اسم الحاوية البعيدة (المكون الإضافي). يجب أن يتطابق هذا الاسم مع الاسم المستخدم في تكوين `remotes` الخاص بالمضيف.
- `filename`: اسم ملف الإدخال البعيد الذي سيجلبه المضيف.
- `exposes`: يحدد الوحدات التي تعرضها الحاوية البعيدة. في هذه الحالة، نعرض وحدة `PluginComponent`. المفتاح './PluginComponent' هو المستخدم في جملة الاستيراد الخاصة بالمضيف (على سبيل المثال، `import('plugin/PluginComponent')`).
- `shared`: تمامًا مثل المضيف، يسرد التبعيات المشتركة. من الضروري أن تكون التبعيات المشتركة وإصداراتها متوافقة بين المضيف والتطبيق البعيد.
أنشئ دليل `src` وأضف ملف `PluginComponent.jsx` بالمحتوى التالي:
import React from 'react';
const PluginComponent = () => {
return (
<div style={{border: '1px solid blue', padding: '10px'}}>
<h2>Plugin Component</h2>
<p>This is a dynamically loaded plugin!</p>
</div>
);
};
export default PluginComponent;
أنشئ ملف `index.js` في دليل `src` لتصدير PluginComponent:
import PluginComponent from './PluginComponent';
export default PluginComponent;
أنشئ دليل `public` وأضف ملف `index.html` بالمحتوى التالي:
<!DOCTYPE html>
<html>
<head>
<title>Plugin Application</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>
أضف ملف تكوين Babel باسم `.babelrc`:
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
حدّث ملف `package.json` الخاص بك بسكريبت بدء التشغيل:
{
"name": "plugin-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"babel-loader": "^9.1.3",
"html-webpack-plugin": "^5.6.0",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
3. تشغيل التطبيقات
ابدأ تشغيل كل من التطبيق المضيف وتطبيق المكون الإضافي عن طريق تشغيل `npm start` في الدلائل الخاصة بكل منهما.
انتقل إلى `http://localhost:3000` في متصفحك. يجب أن ترى التطبيق المضيف مع مكون المكون الإضافي الذي تم تحميله ديناميكيًا.
الميزات المتقدمة والاعتبارات
إدارة الإصدارات والتراجعات
يدعم Module Federation إدارة الإصدارات، مما يسمح لك بإدارة إصدارات مختلفة من المكونات الإضافية. يمكنك تحديد قيود الإصدار في تكوين `remotes` الخاص بالمضيف. على سبيل المثال:
remotes: {
'plugin': 'Plugin@http://localhost:3001/remoteEntry.js@1.0.0',
}
هذا يخبر المضيف باستخدام الإصدار 1.0.0 من المكون الإضافي. إذا توفر إصدار أحدث، فسيستمر المضيف في استخدام الإصدار المحدد حتى يتم تحديثه صراحةً. يعد تنفيذ إدارة إصدارات قوية أمرًا بالغ الأهمية لمنع التغييرات التي قد تؤدي إلى تعطل التطبيق وضمان استقراره.
الاعتبارات الأمنية
عند استخدام Module Federation، يكون الأمان أمرًا بالغ الأهمية. ضع في اعتبارك ما يلي:
- المصادقة والترخيص: قم بتنفيذ آليات مصادقة وترخيص مناسبة لضمان أن المستخدمين المصرح لهم فقط يمكنهم الوصول إلى المكونات الإضافية واستخدامها.
- سلامة الكود: تحقق من سلامة الوحدات البعيدة لمنع حقن الكود الضار في التطبيق. ضع في اعتبارك استخدام سياسة أمان المحتوى (CSP) لتقييد المصادر التي يمكن للتطبيق تحميل الموارد منها.
- إدارة التبعيات: قم بإدارة تبعيات كل من الحاويات المضيفة والبعيدة بعناية لتجنب الثغرات الأمنية. قم بتحديث التبعيات بانتظام إلى أحدث الإصدارات.
- التحقق من صحة المدخلات: تحقق من صحة جميع البيانات المستلمة من الوحدات البعيدة لمنع هجمات الحقن.
- CORS (مشاركة الموارد عبر المصادر): قم بتكوين CORS بشكل صحيح للسماح للتطبيق المضيف بالوصول إلى ملف الإدخال البعيد من تطبيق المكون الإضافي.
اكتشاف وإدارة المكونات الإضافية
بالنسبة لأنظمة المكونات الإضافية الأكثر تعقيدًا، قد تحتاج إلى آلية لاكتشاف وإدارة المكونات الإضافية. يمكن تحقيق ذلك من خلال سجل مكونات إضافية أو خدمة اكتشاف. يمكن لسجل مركزي تخزين معلومات حول المكونات الإضافية المتاحة، بما في ذلك موقعها وإصدارها وتبعياتها. يمكن للتطبيق المضيف بعد ذلك الاستعلام من السجل للعثور على المكونات الإضافية المناسبة وتحميلها.
ضع في اعتبارك هذه الأساليب:
- التكوين المركزي: قم بتخزين عناوين URL للمكونات الإضافية في ملف تكوين مركزي (مثل ملف JSON) يقرأه التطبيق المضيف في وقت التشغيل. يتيح لك هذا إضافة المكونات الإضافية أو إزالتها أو تحديثها بسهولة دون إعادة نشر التطبيق المضيف.
- الاكتشاف القائم على API: قم بإنشاء نقطة نهاية API تُرجع قائمة بالمكونات الإضافية المتاحة. يمكن للتطبيق المضيف بعد ذلك جلب هذه القائمة وتحميل المكونات الإضافية ديناميكيًا.
- البنية القائمة على الأحداث: استخدم ناقل أحداث أو قائمة انتظار رسائل لإعلام التطبيق المضيف عند توفر مكونات إضافية جديدة. يتيح هذا اكتشاف وتحميل المكونات الإضافية بشكل غير متزامن.
التكوين الديناميكي وتفعيل المكونات الإضافية
يعد السماح للمستخدمين بتكوين وتفعيل المكونات الإضافية ديناميكيًا ميزة قوية. يتطلب هذا آلية لتخزين وإدارة تكوينات المكونات الإضافية. يمكنك استخدام قاعدة بيانات أو ملف تكوين أو خدمة تكوين قائمة على السحابة لتخزين إعدادات المكونات الإضافية. يمكن للتطبيق المضيف بعد ذلك قراءة هذه الإعدادات في وقت التشغيل وتفعيل المكونات الإضافية وفقًا لذلك. ضع في اعتبارك توفير واجهة مستخدم لإدارة تكوينات المكونات الإضافية.
التعامل مع العمليات غير المتزامنة ومعالجة الأخطاء
عند العمل مع المكونات الإضافية التي يتم تحميلها ديناميكيًا، من الضروري التعامل مع العمليات غير المتزامنة والأخطاء بأمان. استخدم `async/await` أو Promises لإدارة الكود غير المتزامن. قم بتنفيذ معالجة أخطاء مناسبة لالتقاط وتسجيل أي أخطاء تحدث أثناء تحميل أو تنفيذ المكون الإضافي. قدم رسائل خطأ مفيدة للمستخدم. ضع في اعتبارك استخدام خدمة تسجيل أخطاء مركزية لتتبع الأخطاء عبر جميع المكونات الإضافية.
تقسيم الكود وتحسين الأداء
لتحسين الأداء، استخدم تقسيم الكود لتقسيم التطبيق والمكونات الإضافية إلى أجزاء أصغر. يتيح هذا للمتصفح تنزيل الكود المطلوب فقط لصفحة أو ميزة معينة. يوفر Webpack دعمًا مدمجًا لتقسيم الكود. ضع في اعتبارك استخدام التحميل الكسول لتحميل المكونات الإضافية فقط عند الحاجة إليها. قم بتصغير وضغط الكود لتقليل حجم الملف.
الاختبار والتكامل المستمر
اختبر نظام المكونات الإضافية الخاص بك بدقة للتأكد من أنه يعمل بشكل صحيح. اكتب اختبارات الوحدة واختبارات التكامل واختبارات شاملة. استخدم نظام تكامل مستمر (CI) لتشغيل الاختبارات تلقائيًا كلما تم تغيير الكود. قم بتنفيذ خط أنابيب تسليم مستمر (CD) لأتمتة نشر التطبيق والمكونات الإضافية.
أمثلة من العالم الحقيقي وحالات الاستخدام
يتم استخدام Module Federation في مجموعة متنوعة من التطبيقات الواقعية، بما في ذلك:
- منصات التجارة الإلكترونية: تحميل توصيات المنتجات وبوابات الدفع ومقدمي خدمات الشحن ديناميكيًا. على سبيل المثال، يمكن لمنصة تجارة إلكترونية عالمية استخدام Module Federation لدمج بوابات دفع مختلفة بناءً على موقع العميل. في أمريكا الشمالية، قد تقوم بتحميل مكون إضافي لـ Stripe، بينما في أوروبا، قد تقوم بتحميل مكون إضافي لـ PayPal أو Klarna.
- أنظمة إدارة المحتوى (CMS): السماح للمستخدمين بتثبيت وتفعيل المكونات الإضافية لتوسيع وظائف نظام إدارة المحتوى. يمكن لنظام إدارة المحتوى أن يسمح للمستخدمين بتثبيت مكونات إضافية لتحسين محركات البحث (SEO) أو التكامل مع وسائل التواصل الاجتماعي أو تحليلات المحتوى.
- لوحات المعلومات ومنصات التحليلات: تحميل عناصر واجهة مستخدم وتصورات مختلفة ديناميكيًا. قد تقوم منصة تحليلات عالمية بتحميل مكونات إضافية لمصادر بيانات مختلفة، مثل Google Analytics أو Adobe Analytics أو Salesforce.
- بنى الواجهات الأمامية المصغرة (Microfrontend): بناء تطبيقات ويب واسعة النطاق كمجموعة من الواجهات الأمامية المصغرة القابلة للنشر بشكل مستقل. يمكن لمؤسسة كبيرة استخدام Module Federation لبناء تطبيق الويب الخاص بها كمجموعة من الواجهات الأمامية المصغرة، كل منها مسؤول عن وظيفة عمل محددة، مثل إدارة الحسابات أو كتالوج المنتجات أو معالجة الطلبات.
- أنظمة التصميم: مشاركة مكونات واجهة المستخدم ورموز التصميم عبر تطبيقات متعددة. يمكن لمؤسسة عالمية لديها علامات تجارية متعددة استخدام Module Federation لمشاركة نظام تصميم مشترك عبر جميع تطبيقاتها، مما يضمن الاتساق ويقلل من جهد التطوير.
أفضل الممارسات لبناء أنظمة مكونات إضافية ديناميكية باستخدام Module Federation
فيما يلي بعض أفضل الممارسات التي يجب مراعاتها عند بناء أنظمة مكونات إضافية ديناميكية باستخدام Module Federation:
- اجعل المكونات الإضافية صغيرة ومركزة: يجب أن يكون كل مكون إضافي مسؤولاً عن وظيفة محددة. هذا يسهل صيانة وتحديث المكونات الإضافية.
- حدد واجهات واضحة للمكونات الإضافية: حدد واجهات واضحة لكيفية تفاعل المكونات الإضافية مع التطبيق المضيف. هذا يضمن توافق المكونات الإضافية مع المضيف ويمنع التغييرات التي قد تؤدي إلى تعطل التطبيق.
- استخدم الإصدار الدلالي (Semantic Versioning): استخدم الإصدار الدلالي لإدارة إصدارات المكونات الإضافية الخاصة بك. هذا يسهل تتبع التغييرات وضمان التوافق.
- وفر التوثيق: وفر توثيقًا واضحًا وموجزًا للمكونات الإضافية الخاصة بك. يساعد هذا المستخدمين على فهم كيفية تثبيت وتكوين واستخدام المكونات الإضافية.
- نفذ أفضل الممارسات الأمنية: اتبع أفضل الممارسات الأمنية لحماية تطبيقك ومكوناتك الإضافية من الثغرات الأمنية.
- راقب أداء المكونات الإضافية: راقب أداء المكونات الإضافية لتحديد أي اختناقات. قم بتحسين الكود لتحسين الأداء.
- أتمتة النشر: أتمتة نشر تطبيقك والمكونات الإضافية. هذا يقلل من خطر الأخطاء ويضمن نشر التحديثات بسرعة.
- استخدم أسلوب ترميز متسق: افرض أسلوب ترميز متسقًا عبر جميع المكونات الإضافية. هذا يجعل الكود أسهل في القراءة والصيانة.
- اكتب اختبارات الوحدة: اكتب اختبارات الوحدة للمكونات الإضافية الخاصة بك للتأكد من أنها تعمل بشكل صحيح.
- استخدم مدقق الكود (Linter): استخدم مدقق الكود للتحقق تلقائيًا من وجود أخطاء في الكود الخاص بك.
الخاتمة
يوفر JavaScript Module Federation آلية قوية ومرنة لبناء أنظمة مكونات إضافية ديناميكية. من خلال الاستفادة من Module Federation، يمكنك إنشاء تطبيقات معيارية وقابلة للتطوير والصيانة يمكنها التكيف مع المتطلبات المتغيرة. باتباع أفضل الممارسات الموضحة في هذه المقالة، يمكنك بناء أنظمة مكونات إضافية قوية وآمنة تلبي احتياجات مؤسستك.
هذه التكنولوجيا ذات قيمة خاصة في السياقات الدولية، حيث تمكن الشركات من تكييف عروض برامجها لمناطق أو شرائح عملاء محددة دون الحاجة إلى نشر تطبيقات منفصلة تمامًا. من دمج بوابات الدفع المحلية إلى تقديم محتوى خاص بمنطقة معينة، يسهل Module Federation تجربة مستخدم أكثر تخصيصًا وكفاءة على مستوى العالم.